package com.dify.tool.common.utils;

import cn.hutool.core.text.StrPool;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.dify.tool.common.constant.ErrorStatus;
import com.dify.tool.common.exception.BizException;
import com.spire.doc.*;
import com.spire.doc.documents.HyperlinkType;
import com.spire.doc.documents.Paragraph;
import com.spire.doc.documents.TableRowHeightType;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper;
import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.hwpf.extractor.WordExtractor;
import org.apache.poi.xwpf.extractor.XWPFWordExtractor;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;

@Slf4j
@Component
public class DocTemplateUtils {
    @Value("${dify.url}")
    private String url;

    @Autowired
    private MinioUtil minioUtil;

    private final static String ATTACHMENT_FILE_LINK_HOLDER = "attachmentLinkList";

    public String replaceFileMap(String path, Map<String, Object> dataMap) {
        Document doc = new Document();
        try (InputStream inputStream = new ClassPathResource(path).getInputStream()) {
            doc.loadFromStream(inputStream, FileFormat.Docx);
            //替换内容
            for (Map.Entry<String, Object> entry : dataMap.entrySet()) {
                String k = "${" + entry.getKey() + "}";
                String v = ObjectUtil.isNotEmpty(entry.getValue()) ? String.valueOf(entry.getValue()) : "";
                doc.replace(k, v, true, false);
            }
            log.info("替换模版成功!");
            //设置超链接
            doLinkIn(dataMap, doc);
            ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
            doc.saveToStream(byteOut, FileFormat.Docx);
            return minioUtil.uploadFileAndGetObject(new ByteArrayInputStream(byteOut.toByteArray()), genFileKey(IdUtil.objectId() + StrPool.DOT + "docx"), null);
        } catch (Exception e) {
            log.error("文件生成失败！{}", e.getMessage());
            throw new BizException(ErrorStatus.PROCESS_TEMPLATE_FAIL);
        }
    }

    /**
     * 处理超链接,遍历全文档关键字,定位指定占位符attachmentLinkList的位置插入超链接
     *
     * @param dataMap 参数
     * @param doc     操作的文档
     */
    private void doLinkIn(Map<String, Object> dataMap, Document doc) {
        //遍历整个文档表格，定位占位符位置 进行附件填充
        List<Map<String, String>> resultList = (List<Map<String, String>>) dataMap.get(ATTACHMENT_FILE_LINK_HOLDER);
        if (ObjectUtil.isNotEmpty(resultList)) {
            for (Object section : doc.getSections()) {
                // 遍历文档所有表格
                for (Object table : ((Section) section).getTables()) {
                    // 遍历表格所有行
                    for (Object row : ((Table) table).getRows()) {
                        // 遍历行中所有单元格
                        for (Object cell : ((TableRow) row).getCells()) {
                            // 检查单元格段落是否包含占位符
                            for (Object para : ((TableCell) cell).getParagraphs()) {
                                Paragraph paraObj = (Paragraph) para;
                                if (paraObj.getText().contains(ATTACHMENT_FILE_LINK_HOLDER)) {
                                    // 清空占位符内容
                                    paraObj.getChildObjects().clear();
                                    // 添加超链接
                                    for (Map<String, String> fileInfo : resultList) {
                                        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
                                            try (CloseableHttpResponse fileResponse = httpClient.execute(new HttpGet(url + fileInfo.get("url")))) {
                                                try (InputStream fileInput = fileResponse.getEntity().getContent()) {
                                                    String uploadUrl = minioUtil.uploadFileAndGetObject(fileInput, genFileKey(fileInfo.get("filename")), null);
                                                    Paragraph newPara = ((TableCell) cell).addParagraph();
                                                    newPara.appendHyperlink(uploadUrl, fileInfo.get("filename"), HyperlinkType.Web_Link);
                                                    ((TableRow) row).setHeightType(TableRowHeightType.Auto);
                                                }
                                            }
                                        } catch (Exception e) {
                                            throw new RuntimeException(e);
                                        }
                                    }
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * 生成文件的相对路径，格式如 2021/10/11/1377109572375810050.docx
     *
     * @param fileName 生成的文件名
     */
    public String genFileKey(String fileName) {
        // 获取日期文件夹，格式如，2025/02/11/
        String dateFolderPath = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd/"));
        return StrUtil.SLASH + dateFolderPath + fileName;
    }


    /**
     * 将输入流转换为纯文本（支持PDF/DOC/DOCX）
     *
     * @param inputStream 文件输入流
     * @param fileType    文件类型（pdf/doc/docx）
     * @return 提取的文本内容
     * @throws IOException 文件读取异常
     */
    public static String extractText(InputStream inputStream, String fileType)
            throws IOException {
        return switch (fileType.toLowerCase()) {
            case "pdf" -> extractFromPdf(inputStream);
            case "doc" -> extractFromDoc(inputStream);
            case "docx" -> extractFromDocx(inputStream);
            default -> "";
        };
    }

    private static String extractFromPdf(InputStream inputStream) throws IOException {
        try (PDDocument document = Loader.loadPDF(inputStream.readAllBytes())) {
            return new PDFTextStripper().getText(document);
        }
    }

    private static String extractFromDoc(InputStream inputStream) throws IOException {
        try (HWPFDocument document = new HWPFDocument(inputStream)) {
            return new WordExtractor(document).getText();
        }
    }

    private static String extractFromDocx(InputStream inputStream) throws IOException {
        try (XWPFDocument document = new XWPFDocument(inputStream)) {
            return new XWPFWordExtractor(document).getText();
        }
    }
}
